#include  "doxon.h"
#include  "schedule.h"
#include  "pcb.h"
#include  "thread.h"
#include  "queue.h"
#include  "idle.h"
#include  <dos.h>
#include  <stdio.h>

#include  <_null.h>

volatile char Doxon::dispatched = 0;

void interrupt (*Doxon::old_routine)() = NULL;
volatile PCB* Doxon::running           = NULL;
Thread* Doxon::starting                = NULL;
Idle* Doxon::idle                      = NULL;

void Doxon::init() {
    lock();
    old_routine = replace_int(0x08, timer_int);
    starting = new Thread((UID)-1, (Priority)0, defaultStackSize, minTimeSlice());

    starting->myPCB->state = PCB::READY;
    running = (volatile PCB*)starting->myPCB;

    idle = new Idle();
    idle->start();

    unlock();
}

// Treba proveriti da li su sve niti zavrsene !!!
void Doxon::halt() {
    if ( (PCB*)running != starting->myPCB ) return;

    while ( PCB::exist_other_thr() )
        Doxon::dispatch();
    
    lock();

    replace_int(0x08, old_routine);

    delete starting;
    delete idle;
    unlock();
}

void Doxon::dispatch() {
    lock();

    // radimo eksplicitni dispatch
    dispatched = 1;

    timer_int();

    //dispatched = 0;
    unlock();
}

/* Prototype for TICK */
void tick();

void interrupt Doxon::timer_int() {
    static volatile unsigned int tsp, tss;
    static volatile PCB *newThread;

    /* Ne moramo da koristimo lock() jer interrupt rutina cuva flags reg. */
    asm { cli; }
  
    /* 
     * Ako je ova rutina prouzrokovana prekidom, onda je potrebno da obavestimo
     * test okruzenje o tome i da pozovemo rutinu DOS okruzenja.
     */
    if ( !dispatched ) {
        tick();
        PCB::refresh_blocked();
        (*old_routine)();
    }

    /* Time increment */
    if (!dispatched && running->time_slice != 0)
        running->time_passed += (Time)minTimeSlice();
    /* Izbegava menjanje konteksta, ako nismo pozvali rutinu eksplicitno, il
     * ako proces ima jos vremena da se izvrsava.  I naravno, ako mu je dato
     * beskonacno vreme izvrsavanja.
     */
    if (!dispatched 
         && (running->time_passed < running->time_slice 
             ||  running->time_slice == 0) )
        return;
    if (dispatched) dispatched = 0;

    /* DISPATCH */
    /* Stavlja samo READY niti u scheduler, koje nisu idle */
    if ( running->state == PCB::READY && running != idle->myPCB )
        Scheduler::put((PCB*)running);

    if ( running->state == PCB::FINISHED && running->my_thread != Doxon::starting) {
        PCB::del_from_list( (PCB*)running );
    }

    while(1)
    {
        newThread = Scheduler::get();
        /* Ako je prazan Sheduler, eto posla za IdleThread */
        if (newThread == NULL) newThread = idle->myPCB;

        /* XXX: Kada se ovo moze desiti? */
        if (newThread->state != PCB::READY) continue;
        /* Promena konteksta */
        asm {
            mov tsp, sp;
            mov tss, ss;
        }
        running->sp = tsp;
        running->ss = tss;

        running = newThread;
        tsp = running->sp;
        tss = running->ss;
        asm {
            mov sp, tsp;
            mov ss, tss;
        }
        running->time_passed = 0;
        break;
    }
    return;
}

InterruptRoutine Doxon::replace_int(IVTNo int_num, InterruptRoutine newRoutine) {
    lock();
    unsigned int tseg = FP_SEG(newRoutine);
    unsigned int toff = FP_OFF(newRoutine);

    unsigned int oldseg, odloff;
    InterruptRoutine old;

    int_num *= 4;
    /* Kod dat na vezbama */
    asm {
        push es;
        push ax;
        push bx;

        /* ES = 0 */
        mov ax,0;
        mov es,ax;

        /* BX = int_num * 4 */
        mov bx, word ptr int_num;
        /* pamti staru */
        mov ax, word ptr es:bx+2;
        mov word ptr oldseg, ax;
        mov ax, word ptr es:bx;
        mov word ptr odloff, ax;
        /* postavlja novu */
        mov ax, word ptr tseg;
        mov word ptr es:bx+2, ax;
        mov ax, word ptr toff;
        mov word ptr es:bx, ax;

        pop bx;
        pop ax;
        pop es;
    }
    old = (InterruptRoutine) MK_FP(oldseg,odloff);
    unlock();
    return old;
}
void Doxon::wrapper() {
    running->my_thread->run();
    lock();
    running->state = PCB::FINISHED;
    /*
     * Potrebno je aktivirati sve niti koje su cekale na ovoj, te iz
     * odgovarajuceg reda punimo Sheduler.
      */
    PCB* temp;
    while( running->waitQueue->size() > 0 )
    {
        temp = running->waitQueue->get();
        temp->state = PCB::READY;
        Scheduler::put(temp);
    }
    Doxon::dispatch();
    unlock();
}

